﻿using GEPL.ViewModels;
using System;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Gdi = System.Drawing;
using Image = System.Windows.Controls.Image;

namespace GEPL.Projects
{
    /// <summary>
    /// Represents a "turtle" (a pen) used to draw images
    /// </summary>
    public class TurtleOutput
    {
        /// <summary>
        /// Actual image
        /// </summary>
        public Gdi.Bitmap Bitmap { get; set; }

        /// <summary>
        /// GDI control used to draw image
        /// </summary>
        public Gdi.Graphics Graphics { get; private set; }

        /// <summary>
        /// WPF control used to display image
        /// </summary>
        private readonly Image imageControl;

        private readonly TurtleProjectSettingsModel settings;

        private readonly Gdi.Pen drawingPen;

        // Although the canvas can only represent integer
        // positions, we store our state in double precision.
        // Otherwise, most interesting graphics (that tend to
        // involve recursion and small numbers) look terribly
        // broken.
        private double currentX, currentY;

        private bool penDown;
        private bool showTurtle;
        private int direction;

        /// <summary>
        /// Creates an instance of class
        /// </summary>
        /// <param name="imageControl">Image control displaying bitmap</param>
        /// <param name="settings">Project settings</param>
        public TurtleOutput(Image imageControl, TurtleProjectSettingsModel settings)
        {
            this.imageControl = imageControl;
            this.settings = settings;

            var penColor = settings != null ? settings.PenColor : Gdi.Color.LightGreen;
            drawingPen = new Gdi.Pen(penColor);

            Initialize();
            Refresh();
        }

        /// <summary>
        /// Initializes the output
        /// </summary>
        private void Initialize()
        {
            if (Bitmap != null)
                Bitmap.Dispose();
            if (Graphics != null)
                Graphics.Dispose();

            int width = settings != null ? settings.Width : 900;
            int height = settings != null ? settings.Height : 400;
            Gdi.Color bgcolor = settings != null ? settings.BgColor : Gdi.Color.DarkGreen;

            Bitmap = new Gdi.Bitmap(width, height); // set image resolution here
            Graphics = Gdi.Graphics.FromImage(Bitmap);
            Graphics.Clear(bgcolor);

            currentX = Graphics.VisibleClipBounds.Width / 2.0;
            currentY = Graphics.VisibleClipBounds.Height / 2.0;

            penDown = true;
            showTurtle = true;
            direction = 90;
        }

        /// <summary>
        /// Refreshes the Image control (we have to do it manually
        /// because WPF Image control is not compatible with
        /// old GDI, which was originally used in WinForms)
        /// </summary>
        internal void Refresh()
        {
            var hbmp = Bitmap.GetHbitmap();
            var options = BitmapSizeOptions.FromEmptyOptions();
            imageControl.Source = Imaging.CreateBitmapSourceFromHBitmap(hbmp,
                IntPtr.Zero, Int32Rect.Empty, options);
            imageControl.InvalidateMeasure();
            imageControl.InvalidateVisual();
        }

        /// <summary>
        /// Turns on the pen
        /// </summary>
        public void PenUp()
        {
            penDown = false;
        }

        /// <summary>
        /// Turns off the pen
        /// </summary>
        public void PenDown()
        {
            penDown = true;
        }

        /// <summary>
        /// Draw a straight line (forward)
        /// </summary>
        /// <param name="steps">Length of the line</param>
        public void GoForward(double steps)
        {
            int oldX = (int)currentX;
            int oldY = (int)currentY;

            // In essence, the turtle draws the hypotenuse
            // of a triangle as it moves.  Since the user provides
            // the length of the hypotenuse, we use standard
            // trigonometry to determine the X and Y components
            // of the movement independently.
            currentX += steps * Math.Cos(DegToRad(direction));
            currentY -= steps * Math.Sin(DegToRad(direction));

            if (penDown)
            {
                Graphics.DrawLine(drawingPen, oldX, oldY,
                    (int)currentX, (int)currentY);
            }
        }

        /// <summary>
        /// Draw a straight line (backward)
        /// </summary>
        /// <param name="steps">Length of the line</param>
        public void GoBackward(double steps)
        {
            GoForward(-1 * steps);
        }

        /// <summary>
        /// Rotate the turtle (anti-clockwise)
        /// </summary>
        /// <param name="degrees">Rotation degrees</param>
        public void TurnLeft(int degrees)
        {
            direction = (direction + degrees) % 360;
        }

        /// <summary>
        /// Rotate the turtle (clockwise)
        /// </summary>
        /// <param name="degrees">Rotation degrees</param>
        public void TurnRight(int degrees)
        {
            direction = (direction - degrees + 360) % 360;
        }

        /// <summary>
        /// The turtle icon will be visible
        /// </summary>
        public void ShowTurtle()
        {
            showTurtle = true;
        }

        /// <summary>
        /// The turtle icon will not be visible
        /// </summary>
        public void HideTurtle()
        {
            showTurtle = false;
        }

        /// <summary>
        /// Clear the output and set the turtle in starting position.
        /// </summary>
        public void Reset()
        {
            Initialize();
        }

        /// <summary>
        /// Draws the turtle icon
        /// </summary>
        internal void DrawTurtle()
        {
            if (showTurtle)
            {
                // We leverage the 2d transformations of the .Net
                // Graphics class here to save us from doing the
                // math for rotation contortions ourselves.
                // Rather than draw a rotated turtle, we instead rotate
                // (and reposition) the canvas, then draw a straight
                // turtle.  When we restore the canvas again, the
                // turtle now appears rotated.

                System.Drawing.Drawing2D.GraphicsState canvasState =
                    Graphics.Save();
                Graphics.TranslateTransform((float)currentX, (float)currentY);
                Graphics.RotateTransform(90 - direction);

                Graphics.DrawLine(drawingPen, -4, 4, 0, -8);
                Graphics.DrawLine(drawingPen, 0, -8, 4, 4);
                Graphics.DrawLine(drawingPen, -4, 4, 4, 4);

                Graphics.Restore(canvasState);
            }
        }

        /// <summary>
        /// Converts degrees to radians
        /// </summary>
        /// <param name="degrees">Degrees</param>
        /// <returns>Radians</returns>
        private double DegToRad(int degrees)
        {
            return (Math.PI * degrees / 180.0);
        }
    }
}